package org.gpp.proj1.communication.sender.manager;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.gpp.proj1.communication.ConnectionException;
import org.gpp.proj1.communication.listener.OfflineClientListener;
import org.gpp.proj1.communication.sender.RequestSenderIF;

import com.jme.util.LoggingSystem;

/**
 * 
 * @author Paolo Victor, paolovictor@gmail.com
 * @author Gustavo Farias
 */
public class RMIRequestSenderManager implements RequestSenderManagerIF {

	/**
	 * Thread that pings the currently connected peers' request senders.
	 * If a ping request fails, the peer is considered offline and
	 * the offline listeners notified 
	 */
	protected OfflineClientTrackerThread offLineClientTrackerThread;

	/**
	 * List of entities that must be notified when the offline client
	 * tracker detects an offline client
	 */
	protected List<OfflineClientListener> offlineClientListeners;

	private Map<String, RequestSenderIF> clients;

	public RMIRequestSenderManager() {

		this.clients = new HashMap<String, RequestSenderIF>();

		// Creates and starts the offline client tracker thread
		this.offlineClientListeners = new LinkedList<OfflineClientListener>();
		this.offLineClientTrackerThread = new OfflineClientTrackerThread();		
		new Thread( offLineClientTrackerThread ).start();
	}

	public void notifyConnected(String clientUUId, RequestSenderIF requestSender ) {

		clients.put( clientUUId, requestSender );
	}	

	public void notifyDisconnected(String clientUUId ) {

		fireOfflineClientEvent( clientUUId );

		clients.remove( clientUUId );
	}

	public boolean contains(String clientUUId) {

		return clients.containsKey( clientUUId );
	}

	public RequestSenderIF getSender(String clientUUId) {

		return clients.get( clientUUId );
	}

	public Collection<RequestSenderIF> getSenders() {

		return clients.values();
	}

	public void remove( String clientId ) {
		this.clients.remove( clientId );
	}

	private class OfflineClientTrackerThread implements Runnable {

		private static final long CLIENT_POLL_INTERVAL = 5000;

		private Object monitor;

		private boolean shouldStop;

		public OfflineClientTrackerThread() {

			this.monitor = new Object();
			this.shouldStop = false;
		}

		public void run() {

			while( true ) {
				try {
					synchronized ( monitor ) {
						this.monitor.wait( CLIENT_POLL_INTERVAL );
					}
				} catch (InterruptedException e) {				
					LoggingSystem.getLogger().warning( "OfflineClientTrackerThread wait failed" );
					LoggingSystem.getLogger().throwing( this.getClass().getName(), "run", e );
				}

				if( this.shouldStop ) {
					return;
				}

				synchronized ( clients ) {
					List<String> offlineClients = new LinkedList<String>();

					for( RequestSenderIF sender : clients.values() ) {
						try {
							sender.ping();
						} catch (ConnectionException e) {
							LoggingSystem.getLogger().warning( "Client offline" );
							LoggingSystem.getLogger().throwing( this.getClass().getName(), "run", e);

							offlineClients.add( sender.getServerId() );							
							remove( sender.getServerId() );
						}
					}

					for( String offlineClient : offlineClients ) {
						fireOfflineClientEvent( offlineClient );
					}
				}
			}
		}

		public void wakeUp() {

			synchronized ( monitor ) {
				this.monitor.notify();	
			}			
		}

		public void stop() {

			this.shouldStop = true;

			synchronized ( monitor ) {
				this.monitor.notify();
			}
		}
	}

	public void registerOfflineClientListener(OfflineClientListener listener) {

		this.offlineClientListeners.add( listener );
	}


	private void fireOfflineClientEvent( String clientId ) {

		for( OfflineClientListener listener: offlineClientListeners ) {
			listener.clientIsOffline( clientId );
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.sender.manager.RequestSenderManagerIF#notifyUndeliveredMessage()
	 */
	public void notifyUndeliveredMessage() {

		this.offLineClientTrackerThread.wakeUp();
	}

	/**
	 * Invoked to notify that the local request processor
	 * has been finalized
	 */
	public void notifyDisconnected() {
		synchronized ( clients ) {
			// Sending disconnect message to clients
			for( RequestSenderIF sender : clients.values() ) {

				try {
					sender.disconnect();
				} catch (ConnectionException e) {
					LoggingSystem.getLogger().warning( "Could not notify disconnection" );
					LoggingSystem.getLogger().throwing( this.getClass().getName(), "notifyDisconnected", e);
					e.printStackTrace();
				}
			}

			// Clearing clients map
			clients.clear();
		}
	}

	public void stop() {
		synchronized ( clients ) {
			clients.clear();
		}
		this.offLineClientTrackerThread.stop();
	}
}